/*
async函数的语法规则总体上比较简单，难点是错误处理机制。
返回 Promise 对象

async函数返回一个 Promise 对象。

=> async函数内部return语句返回的值，会成为then方法回调函数的参数。<=

下面代码中，函数f内部return命令返回的值，会被then方法回调函数接收到。
*/
async function f() {
	return 'hello world';
}
f().then(v => console.log(v)) // "hello world"

/*
async函数内部抛出错误，会导致返回的 Promise 对象变为reject状态。
抛出的错误对象会被catch方法回调函数接收到。
*/

async function f() {
	throw new Error('出错了');
}

f().then(
	v => console.log('resolve', v),
	e => console.log('reject', e)
	/*
	reject Error: 出错了  !最好不要使用这种写法，虽然可以装逼!
	建议还是使用 catch来捕捉
	*/
)



/*
Promise 对象的状态变化

async函数返回的 Promise 对象，必须等到内部所有await命令后面的 Promise 对象执行完，才会发生状态改变，
除非遇到return语句或者抛出错误。也就是说，只有async函数内部的异步操作执行完，才会执行then方法指定的回调函数。

下面是一个例子。
函数getTitle内部有三个操作：抓取网页、取出文本、匹配页面标题。
只有这三个操作全部完成，才会执行then方法里面的console.log。
*/
async function getTitle(url) {
	let response = await fetch(url);
	let html = await response.text();
	return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"





/*
await 命令
正常情况下，await命令后面是一个 Promise 对象，!返回该对象的结果。! 
如果不是 Promise 对象，就直接返回对应的值。
await命令的参数是数值123，这时等同于return 123
*/
async function f() {
	// 等同于
	// return 123;
	return await 123;
}

f().then(v => console.log(v))
// 123

/*
另一种情况是，await命令后面是一个thenable对象（即定义了then方法的对象），
那么await会将其等同于 Promise 对象。
下面代码中，await命令后面是一个Sleep对象的实例。
这个实例不是 Promise 对象，但是因为定义了then方法，await会将其视为Promise处理。
*/
class Sleep {
	constructor(timeout) {
		this.timeout = timeout;
	}
	then(resolve, reject) {
		const startTime = Date.now();
		setTimeout(
			() => resolve(Date.now() - startTime),
			this.timeout
		);
	}
}

(async () => {
	const sleepTime = await new Sleep(1000); //会进去找then方法
	console.log(sleepTime);
})();
// 1000



/*
这个例子还演示了如何实现休眠效果。
JavaScript 一直没有休眠的语法，但是借助await命令就可以让程序停顿指定的时间。
下面给出了一个简化的sleep实现。
*/
function sleep(interval) {
	return new Promise(resolve => {
		setTimeout(resolve, interval);
	})
}

// 用法
async function one2FiveInAsync() {
	for (let i = 1; i <= 5; i++) {
		console.log(i);
		await sleep(1000);
	}
}

one2FiveInAsync(); //一秒输出一个



/*
await命令后面的 Promise 对象如果变为reject状态， 则reject的参数会被catch方法的回调函数接收到。
注意，await语句前面没有return，但是reject方法的参数依然传入了catch方法的回调函数。
这里如果在await前面加上return，效果是一样的。
*/
async function f() {
	await Promise.reject('出错了');
}

f()
	.then(v => console.log(v)) //如果是resolve则为undefined  需要在await前加return才行 
	.catch(e => console.log(e)) // 出错了

/*
async 函数内return出去的结果  会被包装成promise对象 在回调函数里参数里 
await 后跟的是promise对象时，不管状态如何 都有返回值（参数） 但是reject在没有return的情况下能被catch捕捉到
resolve则不能  为undefined

任何一个await语句后面的 Promise 对象变为reject状态，那么整个async函数都会中断执行。
下面代码中，第二个await语句是不会执行的，因为第一个await语句状态变成了reject。
*/

async function f() {
	await Promise.reject('出错了'); //函数中断
	await Promise.resolve('hello world'); // 不会执行
}


/*
有时，我们希望即使前一个异步操作失败，也不要中断后面的异步操作。
这时可以将第一个await放在try...catch结构里面，这样不管这个异步操作是否成功，第二个await都会执行。
也可以在await后面的 Promise 对象再跟一个catch方法，处理前面可能出现的错误。
*/
async function f() {
	try {
		await Promise.reject('出错了');
	} catch (e) {}
	return await Promise.resolve('hello world');
}
f().then(v => console.log(v)) // hello world

//另一种方法是await后面的 Promise 对象再跟一个catch方法，处理前面可能出现的错误。
async function f() {
	await Promise.reject('出错了').catch(e => console.log(e));
	return await Promise.resolve('hello world');
}

f().then(v => console.log(v))
// 出错了
// hello world





/*
错误处理

如果await后面的异步操作出错，那么等同于async函数返回的 Promise 对象被reject。
下面代码中，async函数f执行后，await后面的 Promise 对象会抛出一个错误对象，导致catch方法的回调函数被调用，
它的参数就是抛出的错误对象。
具体的执行机制，可以参考后文的“async 函数的实现原理”。
*/

async function f() {
	await new Promise(function(resolve, reject) {
		throw new Error('出错了');
	});
}

f()
	.then(v => console.log(v))
	.catch(e => console.log(e))
// Error：出错了

//防止出错的方法，也是将其放在try...catch代码块之中。  当然建立在出错但不想打断后续代码的情况下

async function f() {
	try {
		await new Promise(function(resolve, reject) {
			throw new Error('出错了');
		});
	} catch (e) {}
	return await ('hello world');
}

//如果有多个await命令，可以统一放在try...catch结构中。

async function main() {
	try {
		const val1 = await firstStep();
		const val2 = await secondStep(val1);
		const val3 = await thirdStep(val1, val2);

		console.log('Final: ', val3);
	} catch (err) {
		console.error(err);
	}
}


/*
下面的例子使用try...catch结构，实现多次重复尝试。
下面代码中，如果await操作成功，就会使用break语句退出循环；如果失败，会被catch语句捕捉，然后进入下一轮循环。
*/

const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
	let i;
	for (i = 0; i < NUM_RETRIES; ++i) {
		try {
			await superagent.get('http://google.com/this-throws-an-error');
			break;
		} catch (err) {}
	}
	console.log(i); // 3
}

test();




/*
第一点，前面已经说过，await命令后面的Promise对象，运行结果可能是rejected，
所以最好把await命令放在try...catch代码块中。
*/

async function myFunction() {
	try {
		await somethingThatReturnsAPromise();
	} catch (err) {
		console.log(err);
	}
}

// 另一种写法

async function myFunction() {
	await somethingThatReturnsAPromise()
		.catch(function(err) {
			console.log(err);
		}); //因为await必须等到该promise拿到结果
}

/*
第二点，多个await命令后面的异步操作，如果不存在继发关系，最好让它们同时触发。

getFoo和getBar是两个独立的异步操作（即互不依赖），被写成继发关系。
这样比较耗时，因为只有getFoo完成以后，才会执行getBar，完全可以让它们同时触发。
*/
let foo = await getFoo();
let bar = await getBar();



/*
下面两种写法，getFoo和getBar都是同时触发，这样就会缩短程序的执行时间。
*/

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise; //异步方法已经被执行过了
let bar = await barPromise;


/*
第三点，await命令只能用在async函数之中，如果用在普通函数，就会报错。
*/
async function dbFuc(db) {
	let docs = [{}, {}, {}];

	// 报错
	/*
	因为await用在普通函数之中了。但是，如果将forEach方法的参数改成async函数，也有问题。
	*/
	docs.forEach(function(doc) {
		await db.post(doc);
	});
}

/*
下面代码可能不会正常工作，原因是这时三个db.post()操作将是并发执行，
也就是同时执行，而不是继发执行。正确的写法是采用for循环。
*/
function dbFuc(db) { //这里不需要 async
	let docs = [{}, {}, {}];

	// 可能得到错误结果
	docs.forEach(async function(doc) {
		await db.post(doc);
	});
	/*
	forEach是将每个遍历项分别同时进行  而for循环是每一步都会等上一步执行完毕
	*/
}

/*
正确写法:

最外层async然后内部for循环上下文仍在函数内
*/

async function dbFuc(db) {
	let docs = [{}, {}, {}];

	for (let doc of docs) {
		await db.post(doc);
	}
}

/*
另一种方法是使用数组的reduce()方法。

reduce()方法的第一个参数是async函数，导致该函数的第一个参数是前一步操作返回的 Promise 对象，
所以必须使用await等待它操作结束。

另外，reduce()方法返回的是docs数组最后一个成员的async函数的执行结果，也是一个 Promise 对象，
导致在它前面也必须加上await。

上面的reduce()的参数函数里面没有return语句，原因是这个函数的主要目的是db.post()操作，不是返回值。
而且async函数不管有没有return语句，总是返回一个 Promise 对象，所以这里的return是不必要的。
*/

async function dbFuc(db) {
	let docs = [{}, {}, {}];

	await docs.reduce(async (_, doc) => { //箭头函数锁定作用域   _为累加值，doc为当前元素
		await _; //因为没有return 所以_一直为数组第一个元素  作用就是将其按步骤执行（reduce依赖累加）
		await db.post(doc);
	}, undefined); //起始值为 undefined 即第一项作为起始值开始累加
	//所以await的作用多用于等待  起本身就是个值  需要return  
}



/*
如果确实希望多个请求并发执行，可以使用Promise.all方法。
当三个请求都会resolved时，下面两种写法效果相同。注意 必须同时都是resolved状态
*/

async function dbFuc(db) {
	let docs = [{}, {}, {}];
	let promises = docs.map((doc) => db.post(doc));

	let results = await Promise.all(promises);
	console.log(results);
}

// 或者使用下面的写法

async function dbFuc(db) {
	let docs = [{}, {}, {}];
	let promises = docs.map((doc) => db.post(doc));

	let results = [];
	for (let promise of promises) {
		results.push(await promise); //等待加自动剥落外壳取出值  
	}
	console.log(results);
}



/*
第四点，async 函数可以保留运行堆栈。

函数a内部运行了一个异步任务b()。
当b()运行的时候，函数a()不会中断，而是继续执行。
等到b()运行结束，可能a()早就运行结束了，b()所在的上下文环境已经消失了。
如果b()或c()报错，错误堆栈将不包括a()。
*/

const a = () => { //普通箭头函数弊端
	b().then(() => c());
};

/*
现在将这个例子改成async函数。
b()运行的时候，a()是暂停执行，上下文环境都保存着。一旦b()或c()报错，错误堆栈将包括a()。
*/

const a = async () => {
	await b();
	c();
};
